/**
 * Created by Neville Kadwa.
 */
package org.shiftone.cdep.asm;

import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.Attribute;
import org.objectweb.asm.Opcodes;
import org.apache.log4j.Logger;
import org.shiftone.cdep.model.ClassInfo;
import org.shiftone.cdep.model.MethodInfo;
import org.shiftone.cdep.ClassNameListener;

import java.util.Arrays;
import java.util.ArrayList;

public class CDepClassVisitor implements ClassVisitor
{
    public static final Logger LOG = Logger.getLogger(CDepClassVisitor.class);

    private ClassInfo classDep = null;
    private String location;

    private CDepSignatureVisitor signatureVisitor   = new CDepSignatureVisitor();
    private CDepMethodVisitor    methodVisitor      = new CDepMethodVisitor(this);
    private ClassNameListener[]  classNameListeners = new ClassNameListener[0];

    public CDepClassVisitor()
    {
    }

    public ClassInfo getClassDependencyInfo()
    {
        return classDep;
    }

    public void visit(int version, int access, String name, String signature, String superName, String[] interfaces)
    {
        LOG.debug("visit: " + name + ", " + signature + ", " + superName);
        classDep = new ClassInfo(this.location, name, superName, (access & Opcodes.ACC_INTERFACE) != 0);
        fireClassNameListeners(name);
        if (superName != null)
        {
            fireClassNameListeners(superName);
        }
        signatureVisitor.runSignature(classDep, signature, false);
        for (int i = 0; i < interfaces.length; i++)
        {
            classDep.addImplementsClass(interfaces[i]);
            fireClassNameListeners(interfaces[i]);
        }
    }

    public void visitSource(String source, String debug)
    {
//        LOG.debug("visitSource: " + source + ", " + debug);
    }

    public AnnotationVisitor visitAnnotation(String desc, boolean visible)
    {
        return null;  //To change body of implemented methods use File | Settings | File Templates.
    }

    public void visitAttribute(Attribute attr)
    {
        LOG.debug("visitAttribute: " + attr);
    }

    public void visitInnerClass(String name, String outerName, String innerName, int access)
    {
        LOG.debug("visitInnerClass: " + name + ", " + outerName + ", " + innerName + ", " + access);
    }

    public void visitOuterClass(String owner, String name, String desc)
    {
        LOG.debug("visitOuterClass: " + owner + ", " + name + ", " + desc);
        classDep.setInner(true);
    }

    public FieldVisitor visitField(int access, String name, String desc, String signature, Object value)
    {
        LOG.debug("visitField: " + access + ", " + name + ", " + desc + ", " + signature);
        signatureVisitor.runSignature(classDep, desc, true);
        return null;
    }

    public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions)
    {
        LOG.debug("visitMethod: " + access + ", " + name + ", " + desc + ", " + signature);
        if ((access & Opcodes.ACC_SYNTHETIC) != 0)
            return null;
        signatureVisitor.runSignature(classDep, desc, false);
        MethodInfo methodDep = new MethodInfo(name, desc);
        classDep.addMethod(methodDep);
        methodVisitor.setMethodDep(methodDep);
        return methodVisitor;
    }

    public void visitEnd()
    {
        LOG.debug("visitEnd:");
    }

    public void setLocation(String location)
    {
        this.location = location;
    }

    public void addClassNameListener(ClassNameListener listener)
    {
        ArrayList listenerList = new ArrayList(Arrays.asList(classNameListeners));
        listenerList.add(listener);
        classNameListeners = (ClassNameListener[]) listenerList.toArray(new ClassNameListener[listenerList.size()]);
    }

    public void removeClassNameListener(ClassNameListener listener)
    {
        ArrayList listenerList = new ArrayList(Arrays.asList(classNameListeners));
        listenerList.remove(listener);
        classNameListeners = (ClassNameListener[]) listenerList.toArray(new ClassNameListener[listenerList.size()]);
    }

    void fireClassNameListeners(String className)
    {
        for (int i = 0; i < classNameListeners.length; i++)
        {
            classNameListeners[i].encounterClassName(className);
        }
    }
}
